using System.Collections.Generic;
using Unity.Collections;
using Unity.Deformations;
using Unity.Entities;
using Unity.Entities.Hybrid.Baking;
using Unity.Entities.Serialization;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
#if UNITY_EDITOR
#endif
using UnityEngine;
using BinaryReader = Unity.Entities.Serialization.BinaryReader;

// ReSharper disable once CheckNamespace
public static class SerializeTool
{
    #region DeserializeScene
    
    public static Entity DeserializeScene(byte[] bytes, Unity.Scenes.ReferencedUnityObjects objRefs,
        EntityManager manager)
    {
        BinaryReader reader;
        unsafe
        {
            fixed (byte* ptr = bytes)
            {
                reader = new MemoryBinaryReader(ptr, bytes.Length);
            }
        }

        return DeserializeScene(reader, objRefs, manager);
    }

    private static Entity DeserializeScene(BinaryReader reader, Unity.Scenes.ReferencedUnityObjects objRefs,
        EntityManager manager)
    {
        bool validRefObjects = true;
        do
        {
            if (objRefs == null)
            {
                Debug.LogError($"objRefs is null!");
                validRefObjects = false;
                break;
            }

            if (objRefs.Array == null)
            {
                Debug.LogError($"objRefs.Array is null!");
                break;
            }

            for (int ii = 0; ii < objRefs.Array.Length; ++ii)
            {
                if (objRefs.Array[ii] == null)
                {
                    Debug.LogError($"Invalid ref object data!");
                    validRefObjects = false;
                    break;
                }
            }

        } while (false);

        if(!validRefObjects)
        {
            Debug.LogError($"Invalid ref object data!");
        }
        return DeserializeSceneByReader(reader, objRefs, manager);
    }

    private static Entity DeserializeSceneByReader(BinaryReader reader,
        Unity.Scenes.ReferencedUnityObjects objRefs, EntityManager manager)
    {
        using var deserializeWorld = new World("Deserialize World");
        var deserializeWorldEntityMgr = deserializeWorld.EntityManager;

        DeserializeObjectReferences(objRefs, out Object[] objectReferences);
        var transaction = deserializeWorldEntityMgr.BeginExclusiveEntityTransaction();
        // ReSharper disable once CoVariantArrayConversion
        SerializeUtility.DeserializeWorld(transaction, reader, objectReferences);
        deserializeWorldEntityMgr.EndExclusiveEntityTransaction();
        
        manager.MoveEntitiesFrom(out var loadedEntities, deserializeWorldEntityMgr);

        NativeList<Entity> sceneRootEntities = CleanupBakingEntities(loadedEntities, manager, Allocator.Temp);
        
        Entity sceneRoot = manager.CreateEntity();
        manager.AddComponentData(sceneRoot, LocalTransform.Identity);
        manager.AddComponentData(sceneRoot, new LocalToWorld()
        {
            Value = new float4x4(
                1.0f, 0.0f, 0.0f, 0.0f,
                0.0f, 1.0f, 0.0f, 0.0f,
                0.0f, 0.0f, 1.0f, 0.0f,
                0.0f, 0.0f, 0.0f, 1.0f)
        });
        DynamicBuffer<LinkedEntityGroup> linkedEntityGroups = manager.AddBuffer<LinkedEntityGroup>(sceneRoot);
        for (int i = 0; i < loadedEntities.Length; ++i)
        {
            Entity loadedEntity = loadedEntities[i];
            if (loadedEntity == Entity.Null)
            {
                continue;
            }
            LinkedEntityGroup group = new LinkedEntityGroup()
            {
                Value = loadedEntities[i]
            };
            linkedEntityGroups.Add(group);
        }
        loadedEntities.Dispose();
        for (int j = 0; j < sceneRootEntities.Length; ++j)
        {
            Entity e = sceneRootEntities[j];
            if (!manager.HasComponent<LocalToWorld>(e))
            {
                continue;
            }
            manager.AddComponentData(e, new Parent()
            {
                Value = sceneRoot
            });
        }
        sceneRootEntities.Dispose();
        
        return sceneRoot;
    }
    
    #endregion DeserializeScene
    
    #region Deserialize
    
    public static Entity Deserialize(byte[] bytes, Unity.Scenes.ReferencedUnityObjects objRefs, EntityManager manager)
    {
        BinaryReader reader;
        unsafe
        {
            fixed (byte* ptr = bytes)
            {
                reader = new MemoryBinaryReader(ptr, bytes.Length);
            }
        }
        return Deserialize(reader, objRefs, manager);
    }
    
    private static Entity Deserialize(BinaryReader reader, Unity.Scenes.ReferencedUnityObjects objRefs, EntityManager manager)
    {
        bool validRefObjects = true;
        do
        {
            if (objRefs == null)
            {
                Debug.LogError($"objRefs is null!");
                validRefObjects = false;
                break;
            }

            if (objRefs.Array == null)
            {
                Debug.LogError($"objRefs.Array is null!");
                break;
            }

            for (int ii = 0; ii < objRefs.Array.Length; ++ii)
            {
                if (objRefs.Array[ii] == null)
                {
                    Debug.LogError($"Invalid ref object data!");
                    validRefObjects = false;
                    break;
                }
            }

        } while (false);

        if(!validRefObjects)
        {
            Debug.LogError($"Invalid ref object data!");
        }
        
        return DeserializeByReader(reader, objRefs, manager);
    }
    
    private static Entity DeserializeByReader(BinaryReader reader, Unity.Scenes.ReferencedUnityObjects objRefs, EntityManager manager)
    {
        using var deserializeWorld = new World("Deserialize World");
        var deserializeWorldEntityMgr = deserializeWorld.EntityManager;

        DeserializeObjectReferences(objRefs, out Object[] objectReferences);
        var transaction = deserializeWorldEntityMgr.BeginExclusiveEntityTransaction();
        // ReSharper disable once CoVariantArrayConversion
        SerializeUtility.DeserializeWorld(transaction, reader, objectReferences);
        deserializeWorldEntityMgr.EndExclusiveEntityTransaction();
        
        manager.MoveEntitiesFrom(out var loadedEntities, deserializeWorldEntityMgr);
        
        NativeList<Entity> rootEntities = CleanupBakingEntities(loadedEntities, manager, Allocator.Temp);
        loadedEntities.Dispose();
            
        // 这里返回的Entities，是所有的Entities，需要进行一步筛选，找到唯一的那个就行了。
        Entity loadedEntityPrefab = Entity.Null;
        for (int i = 0; i < rootEntities.Length; ++i)
        {
            var entity = rootEntities[i];
            if (!manager.HasComponent<AssetComponents>(entity))
            {
                continue;
            }
            loadedEntityPrefab = entity;
            break;
        }

        if (loadedEntityPrefab == Entity.Null)
        {
            if (rootEntities.Length > 0)
            {
                loadedEntityPrefab = rootEntities[0];
            }
            if (loadedEntityPrefab == Entity.Null)
            {
                Debug.LogError($"[SeializeTool.DeserializeByReader] Fail to load prefab!");
            }
        }
        rootEntities.Dispose();
        
        return loadedEntityPrefab;
    }
    
    #endregion Deserialize

    private static NativeList<Entity> CleanupBakingEntities(NativeArray<Entity> loadedEntities, EntityManager manager, Allocator allocator)
    {
        NativeList<Entity> rootEntities = new NativeList<Entity>(allocator);
        for (int j = 0; j < loadedEntities.Length; ++j)
        {
            Entity e = loadedEntities[j];
            if (!manager.Exists(e))
            {
                manager.DestroyEntity(e);
                loadedEntities[j] = Entity.Null;
                continue;
            }
            if (!manager.HasComponent<TransformAuthoring>(e))
            {
                rootEntities.Add(e);
                continue;
            }
            TransformAuthoring transAuthoring = manager.GetComponentData<TransformAuthoring>(e);
            if (!manager.HasComponent<LocalToWorld>(e))
            {
                manager.AddComponentData(e, new LocalToWorld()
                {
                    Value = transAuthoring.LocalToWorld
                });
            }
            else
            {
                manager.SetComponentData(e, new LocalToWorld()
                {
                    Value = transAuthoring.LocalToWorld
                });
            }

            if (!manager.HasComponent<LightMaps>(e))
            {
                if (!manager.HasComponent<LocalTransform>(e))
                {
                    manager.AddComponentData(e, new LocalTransform()
                    {
                        Position = transAuthoring.LocalPosition,
                        Rotation = transAuthoring.LocalRotation,
                        Scale = transAuthoring.LocalScale.x
                    });
                }
                else
                {
                    manager.SetComponentData(e, new LocalTransform()
                    {
                        Position = transAuthoring.LocalPosition,
                        Rotation = transAuthoring.LocalRotation,
                        Scale = transAuthoring.LocalScale.x
                    });
                }
            }
            
            if (manager.HasComponent<AdditionalEntitiesBakingData>(e))
            {
                manager.RemoveComponent<AdditionalEntitiesBakingData>(e);
            }
            if (manager.HasComponent<AdditionalEntityParent>(e))
            {
                manager.RemoveComponent<AdditionalEntityParent>(e);
            }
            if (manager.HasComponent<EntityGuid>(e))
            {
                manager.RemoveComponent<EntityGuid>(e);
            }
            if (manager.HasComponent<SceneSection>(e))
            {
                manager.RemoveComponent<SceneSection>(e);
            }
            if (transAuthoring.AuthoringParent == Entity.Null)
            {
                rootEntities.Add(e);
            }
            else
            {
                // SkinnedMesh的父节点比较特殊
                if (manager.HasBuffer<SkinMatrix>(transAuthoring.AuthoringParent) || manager.HasBuffer<SkinMatrix>(e) || manager.HasComponent<LightMaps>(e))
                {
                    DynamicBuffer<Child> eChildren = manager.HasBuffer<Child>(transAuthoring.AuthoringParent)
                        ? manager.GetBuffer<Child>(transAuthoring.AuthoringParent)
                        : manager.AddBuffer<Child>(transAuthoring.AuthoringParent);
                    eChildren.Add(new Child()
                    {
                        Value = e
                    });
                }
                else
                {
                    manager.AddComponentData(e, new Parent()
                    {
                        Value = transAuthoring.AuthoringParent
                    });
                }
            }
            if (manager.HasComponent<LightMaps>(e))
            {
                manager.RemoveComponent<LightMaps>(e);
            }
            manager.RemoveComponent<TransformAuthoring>(e);
        }
        return rootEntities;
    }
    
    private static void DeserializeObjectReferences(Unity.Scenes.ReferencedUnityObjects objRefs, out Object[] objectReferences)
    {
        if (objRefs == null)
        {
            objectReferences = null;
            return;
        }
        objectReferences = objRefs.Array.Clone() as Object[];

        // NOTE: Object references must not include fake object references, they must be real null.
        // The Unity.Properties deserializer can't handle them correctly.
        // We might want to add support for handling fake null,
        // but it would require tight integration in the deserialize function so that a correct fake null unityengine.object can be constructed on deserialize
        if (objectReferences == null)
        {
            return;
        }
        // When using bundles, the Companion GameObjects cannot be directly used (prefabs), so we need to instantiate everything.
        var sourceToInstance = new Dictionary<GameObject, GameObject>();
        for (int i = 0; i != objectReferences.Length; i++)
        {
            Object objRef = objectReferences[i];
            if (objRef == null)
            {
                objectReferences[i] = null;
                continue;
            }
            if (objRef is GameObject source)
            {
                var instance = Object.Instantiate(source);
                objectReferences[i] = instance;
                sourceToInstance.Add(source, instance);
            }
        }
        for (int i = 0; i != objectReferences.Length; i++)
        {
            Object objRef = objectReferences[i];
            if (objRef is Component component)
            {
                objectReferences[i] = sourceToInstance[component.gameObject].GetComponent(component.GetType());
            }
        }
    }
    
    public static string ToUNIXStyle(this string str)
    {
        return string.IsNullOrEmpty(str) ? string.Empty : str.Replace("\\", "/");
    }
}